package com.jcx.ldzj.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 业精于勤荒于嬉，行成于思毁于随
 *
 * @author ld
 * @version 1.0
 * @date 2021/8/1 12:42
 */
@Configuration
@EnableCaching // 开启缓存支持
public class cacheRedisConfig extends CachingConfigurerSupport {
    @Resource
    private LettuceConnectionFactory lettuceConnectionFactory;


    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * @CacheEvict:清除缓存 allEntries:是否清空所有缓存内容，缺省为 false，如果指定为 true，则方法调用后将立即清空所有缓存
     * beforeInvocation:是否在方法执行前就清空，缺省为 false，如果指定为 true，则在方法还没有执行的时候就清空缓存，
     * 缺省情况下，如果方法执行抛出异常，则不会清空缓存
     *
     * @CachePut:每次都执行方法体并对返回结果进行缓存操作
     *         @CachePut(value = "userLoginIdentity",key = "#user.getId()",condition="#user.getUserName().length()>4")
     *
     * @Cacheable:主要针对方法配置，能够根据方法的请求参数对其结果进行缓存
     *          * #：SpEL表达式
     *          * 至少指定一个缓存的名称（value），多个:(value={"cache1","cache2"})
     *          * 若缓存的key为空则缺省按照方法的所有参数进行组合，多个key组合：key="#userName.concat(#password)"
     *
     *
     * condition:缓存条件，可为空  condition="#userName.length()>4"
     *@Cacheable不能配置 condition = "#result != null" ，因为这个注解在进入方法前去检测condition，而这时还没有result，会造成result为null的情况。
     * @CacheEvict 想要需要配置condition = "#result != null"时 ，同时需要配置 beforeInvocation = false。beforeInvocation 的意思为 是否在方法执行前就删除缓存,缺省为 false。如果不配置这两个，会造成方法返回为空时，也会删除缓存，但是返回值都为空了，还会不会有缓存呢？所以这个看具体情况了。
     * @Cacheput 使用condition = "#result != null" 没问题。
     *
     * condition指定缓存的条件(对参数判断，满足什么条件时才缓存)，可用SpEL表达式，
     * 例如：方法入参为对象user则表达式可以写为condition = "#user.age>18"，表示当入参对象user的属性age大于18才进行缓存。
     * unless否定缓存的条件(对结果判断，满足什么条件时不缓存)，即满足unless指定的条件时，对调用方法获取的结果不进行缓存，
     * 例如：unless = "result==null"，表示如果结果为null时不缓存。
     * sync是否使用异步模式进行缓存，默认false。
     *
     */
    // 缓存管理器
    @Bean
    public CacheManager cacheManager(RedisTemplate<String,Object> redisTemplate) {
       //配置菜单缓存过期时间为1小时缓存，同时也可以在这里配置其他过期时间
        Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
        //configurationMap.put("menuCache",getCacheConfigurationWithTtl(template, 60));
        TtlRedisCacheManager ttlRedisCacheManager = new TtlRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(lettuceConnectionFactory),
                // 默认缓存配置
                this.getRedisCacheConfigurationWithTtl(60),
                configurationMap);
            return ttlRedisCacheManager;
    }

    /**
     * 设置默认配置
     * @param seconds
     * @return
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        // 设置 json 序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).
                // 设置过期时间
                        entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }

    /**
     * 自定义配置多个缓存空间，与注解模式的不冲突，因为有重写RedisCacheManager的createRedisCache方法
     * 但不建议两种都使用，容易混淆
     * @param redisTemplate
     * @param seconds
     * @return
     */
    RedisCacheConfiguration getCacheConfigurationWithTtl(RedisTemplate<String, Object> redisTemplate, long seconds) {

        return RedisCacheConfiguration
                .defaultCacheConfig()
                // 设置key为String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
                // 设置value 为自动转Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
                // 不缓存null
                .disableCachingNullValues()
                // 缓存数据保存1小时
                .entryTtl(Duration.ofSeconds(seconds));
    }
}
